Command Injection

Many web applications often interact with the underlying OS directly in order to access and provide various services. If user input is passed unsanitised to these APIs, it can result in command injection, whereby an adversary can inject commands to be executed by the OS on the server.

Exploiting a command injection vulnerability is fairly simple. One simply needs to use command chaining operators to insert OS commands into the unsanitised input. The characters &, &&, |, || function as command separators on both Windows and Unix-based systems. Furthermore, Unix-based systems also use the ; character and new lines as command separators and allow for inline command execution by inserting the command in backticks or dollar signs ($(command)).

Example

Let's look at a simple example using this PortSwigger lab. On the /product page we notice that there is a way to check the number of stock available in a particular city.

When we intercept the request with BurpSuite and test both fields for command injection, we find that the storeId field is vulnerable.

Tip

Sometimes the injection point might be in the middle of the OS command and so you need to append a comment character in order to make the system ignore everything after your command. On Unix-based systems this can be done with the syntax COMMAND #.

Blind Command Injection

In many cases it is not immediately obvious whether command injection is present because there is no way to directly see the output of the command. The vulnerability remains basically unchanged but the detection methods vary.

One can use time delays to check for blind command injection by using a timed command and checking the response time against the specified delay. One way to achieve this is to use the ping command with the -c option which allows one to specify how long (in seconds) the ping command should run.

Example: Time-Based Command Injection

On this PortSwigger lab we find a feedback page:

By messing around with the parameters in the POST request, we find that the email parameter is vulnerable to command injection.

This can be deduced from the response time - 9 514 milliseconds, or approximately 10 seconds, as specified by the ping command.

Notice that we had to use the # (%23) character here to comment out anything after the ping command, since the application returned an error otherwise.

Another way to test for blind command is to use output redirects by redirecting the output of the command to a file in the web root. This file can then be retrieved by navigating to it.

Example: Output-redirected Command Injection

In this PortSwigger lab we again find a vulnerable email parameter:

We are told that the /var/www/images directory is writable but we cannot directly read it, so we leverage an LFI in the request which returns the image of a product:

Yet another method to test for blind command injection is to use out-of-band exfiltration techniques.

Bypassing Filters

Very commonly applications filter out whitespaces before passing the command to the shell. However, certain command sequences will be translated to whitespace in the shell itself.

Under Linux, one can substitute any white space with $IFS or ${IFS}. Alternatively, one can specify the command and its parameters in curly brackets - {command,param1,param2}. The brackets will be removed and the commas will be treated as whitespaces.

Prevention

Ideally, one should never execute OS commands directly from the application, since these can almost always be replaced via safer platform APIs. If this cannot be done, then one should abide by the following guidelines:

  • Validate the user input against a whitelist of permitted values.
  • Validate that the user input follows the expected format (a number, an alphanumeric character, etc.)

Warning

Do not try to escape shell-related characters, for this is too error-prone.